/*
 * libid3tag - ID3 tag manipulation library
 * Copyright (C) 2000-2004 Underbit Technologies, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * $Id: tag.c,v 1.20 2004/02/17 02:04:10 rob Exp $
 */

# ifdef HAVE_CONFIG_H
#  include "config.h"
# endif

# include "global.h"

# include <string.h>
# include <stdlib.h>

# ifdef HAVE_ASSERT_H
#  include <assert.h>
# endif

# include "id3tag.h"
# include "tag.h"
# include "frame.h"
# include "compat.h"
# include "parse.h"
# include "render.h"
# include "latin1.h"
# include "ucs4.h"
# include "genre.h"
# include "crc.h"
# include "field.h"
# include "util.h"

/*
 * NAME:	tag->new()
 * DESCRIPTION:	allocate and return a new, empty tag
 */
struct id3_tag *id3_tag_new(void) {
	struct id3_tag *tag;

	tag = malloc(sizeof(*tag));
	if (tag) {
		tag->refcount      = 0;
		tag->version       = ID3_TAG_VERSION;
		tag->flags         = 0;
		tag->extendedflags = 0;
		tag->restrictions  = 0;
		// setting ID3_TAG_OPTION_UNSYNCHRONISATION causes problems in Winamp when there is an embedded image file
		tag->options       = 0;
		tag->options       = ID3_TAG_OPTION_UNSYNCHRONISATION |
							 /*ID3_TAG_OPTION_COMPRESSION |*/ ID3_TAG_OPTION_CRC;	 // using compression caused some problems when text strings were compressed, so disabling this
		tag->nframes       = 0;
		tag->frames        = 0;
		tag->paddedsize    = 0;
	}

	return tag;
}

/*
 * NAME:	tag->delete()
 * DESCRIPTION:	destroy a tag and deallocate all associated memory
 */
void id3_tag_delete(struct id3_tag *tag)
{
	assert(tag);

	if (tag->refcount == 0) {
		id3_tag_clearframes(tag);

		if (tag->frames)
			free(tag->frames);

		free(tag);
	}
}

/*
 * NAME:	tag->addref()
 * DESCRIPTION:	add an external reference to a tag
 */
void id3_tag_addref(struct id3_tag *tag)
{
	assert(tag);

	++tag->refcount;
}

/*
 * NAME:	tag->delref()
 * DESCRIPTION:	remove an external reference to a tag
 */
void id3_tag_delref(struct id3_tag *tag)
{
	assert(tag && tag->refcount > 0);

	--tag->refcount;
}

/*
 * NAME:	tag->version()
 * DESCRIPTION:	return the tag's original ID3 version number
 */
unsigned int id3_tag_version(struct id3_tag const *tag)
{
	assert(tag);

	return tag->version;
}

/*
 * NAME:	tag->options()
 * DESCRIPTION:	get or set tag options
 */
int id3_tag_options(struct id3_tag *tag, int mask, int values)
{
	assert(tag);

	if (mask)
		tag->options = (tag->options & ~mask) | (values & mask);

	return tag->options;
}

/*
 * NAME:	tag->setlength()
 * DESCRIPTION:	set the minimum rendered tag size
 */
void id3_tag_setlength(struct id3_tag *tag, id3_length_t length)
{
	assert(tag);

	tag->paddedsize = length;
}

/*
 * NAME:	tag->clearframes()
 * DESCRIPTION:	detach and delete all frames associated with a tag
 */
void id3_tag_clearframes(struct id3_tag *tag)
{
	unsigned int i;

	assert(tag);

	for (i = 0; i < tag->nframes; ++i) {
		id3_frame_delref(tag->frames[i]);
		id3_frame_delete(tag->frames[i]);
	}

	tag->nframes = 0;
}

/*
 * NAME:	tag->attachframe()
 * DESCRIPTION:	attach a frame to a tag
 */
int id3_tag_attachframe(struct id3_tag *tag, struct id3_frame *frame)
{
	struct id3_frame **frames;

	assert(tag && frame);

	frames = realloc(tag->frames, (tag->nframes + 1) * sizeof(*frames));
	if (frames == 0)
		return -1;

	tag->frames = frames;
	tag->frames[tag->nframes++] = frame;

	id3_frame_addref(frame);

	return 0;
}

/*
 * NAME:	tag->detachframe()
 * DESCRIPTION:	detach (but don't delete) a frame from a tag
 */
int id3_tag_detachframe(struct id3_tag *tag, struct id3_frame *frame)
{
	unsigned int i;

	assert(tag && frame);

	for (i = 0; i < tag->nframes; ++i) {
		if (tag->frames[i] == frame)
			break;
	}

	if (i == tag->nframes)
		return -1;

	--tag->nframes;
	while (i++ < tag->nframes)
		tag->frames[i - 1] = tag->frames[i];

	id3_frame_delref(frame);

	return 0;
}

/*
 * NAME:	tag->findframe()
 * DESCRIPTION:	find in a tag the nth (0-based) frame with the given frame ID
 * 				if no id specified, return the nth frame.
 */
struct id3_frame *id3_tag_findframe(struct id3_tag const *tag,
									char const *id, unsigned int index) {
	unsigned int len, i;

	assert(tag);

	if (id == 0 || *id == 0)
		return (index < tag->nframes) ? tag->frames[index] : 0;

	len = (int)strlen(id);

	if (len == 4) {
		struct id3_compat const *compat;

		compat = id3_compat_lookup(id, len);
		if (compat && compat->equiv && !compat->translate) {
			id  = compat->equiv;
			len = (int)strlen(id);
		}
	}

	for (i = 0; i < tag->nframes; ++i) {
		if (strncmp(tag->frames[i]->id, id, len) == 0 && index-- == 0)
			return tag->frames[i];
	}

	return 0;
}

enum tagtype {
	TAGTYPE_NONE = 0,
	TAGTYPE_ID3V1,
	TAGTYPE_ID3V2,
	TAGTYPE_ID3V2_FOOTER
};

static
enum tagtype tagtype(id3_byte_t const *data, id3_length_t length)
{
	if (length >= 3 &&
			data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
		return TAGTYPE_ID3V1;

	if (length >= 10 &&
			((data[0] == 'I' && data[1] == 'D' && data[2] == '3') ||
			 (data[0] == '3' && data[1] == 'D' && data[2] == 'I')) &&
			data[3] < 0xff && data[4] < 0xff &&
			data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
		return data[0] == 'I' ? TAGTYPE_ID3V2 : TAGTYPE_ID3V2_FOOTER;

	return TAGTYPE_NONE;
}

static
void parse_header(id3_byte_t const **ptr,
				  unsigned int *version, int *flags, id3_length_t *size)
{
	*ptr += 3;

	*version = id3_parse_uint(ptr, 2);
	*flags   = id3_parse_uint(ptr, 1);
	*size    = id3_parse_syncsafe(ptr, 4);
}

/*
 * NAME:	tag->query()
 * DESCRIPTION:	if a tag begins at the given location, return its size
 */
signed long id3_tag_query(id3_byte_t const *data, id3_length_t length)
{
	unsigned int version;
	int flags;
	id3_length_t size;
	signed long val;

	assert(data);

	switch (tagtype(data, length)) {
		case TAGTYPE_ID3V1:
			return 128;

		case TAGTYPE_ID3V2:
			parse_header(&data, &version, &flags, &size);

			if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
				size += 10;

			return 10 + (int)size;

		case TAGTYPE_ID3V2_FOOTER:
			parse_header(&data, &version, &flags, &size);
			val = (int)size;
			return (-val - 10);

		case TAGTYPE_NONE:
			break;
	}

	return 0;
}

static
void trim(char *str)
{
	char *ptr;

	ptr = str + strlen(str);
	while (ptr > str && ptr[-1] == ' ')
		--ptr;

	*ptr = 0;
}

static
int v1_attachstr(struct id3_tag *tag, char const *id,
				 char *text, unsigned long number)
{
	struct id3_frame *frame;
	id3_ucs4_t ucs4[31];

	if (text) {
		trim(text);
		if (*text == 0)
			return 0;
	}

	frame = id3_frame_new(id);
	if (frame == 0)
		return -1;

	if (id3_field_settextencoding(&frame->fields[0],
								  ID3_FIELD_TEXTENCODING_ISO_8859_1) == -1)
		goto fail;

	if (text)
		id3_latin1_decode((unsigned char *)text, ucs4);
	else
		id3_ucs4_putnumber(ucs4, number);

	if (strcmp(id, ID3_FRAME_COMMENT) == 0) {
		if (id3_field_setlanguage(&frame->fields[1], "XXX") == -1 ||
				id3_field_setstring(&frame->fields[2], id3_ucs4_empty) == -1 ||
				id3_field_setfullstring(&frame->fields[3], ucs4) == -1)
			goto fail;
	} else {
		id3_ucs4_t *ptr = ucs4;

		if (id3_field_setstrings(&frame->fields[1], 1, &ptr) == -1)
			goto fail;
	}

	if (id3_tag_attachframe(tag, frame) == -1)
		goto fail;

	return 0;

fail:
	id3_frame_delete(frame);
	return -1;
}

static
struct id3_tag *v1_parse(id3_byte_t const *data) {
	struct id3_tag *tag;

	tag = id3_tag_new();
	if (tag) {
		char title[31], artist[31], album[31], year[5], comment[31];
		unsigned int genre, track;

		tag->version = 0x0100;

		tag->options |=  ID3_TAG_OPTION_ID3V1;
		tag->options &= ~ID3_TAG_OPTION_COMPRESSION;

		tag->restrictions =
			ID3_TAG_RESTRICTION_TEXTENCODING_LATIN1_UTF8 |
			ID3_TAG_RESTRICTION_TEXTSIZE_30_CHARS;

		title[30] = artist[30] = album[30] = year[4] = comment[30] = 0;

		memcpy(title,   &data[3],  30);
		memcpy(artist,  &data[33], 30);
		memcpy(album,   &data[63], 30);
		memcpy(year,    &data[93],  4);
		memcpy(comment, &data[97], 30);

		genre = data[127];

		track = 0;
		if (comment[28] == 0 && comment[29] != 0) {
			track = comment[29];
			tag->version = 0x0101;
		}

		/* populate tag frames */

		if (v1_attachstr(tag, ID3_FRAME_TITLE,  title,  0) == -1 ||
				v1_attachstr(tag, ID3_FRAME_ARTIST, artist, 0) == -1 ||
				v1_attachstr(tag, ID3_FRAME_ALBUM,  album,  0) == -1 ||
				v1_attachstr(tag, ID3_FRAME_YEAR,   year,   0) == -1 ||
				(track        && v1_attachstr(tag, ID3_FRAME_TRACK, 0, track) == -1) ||
				(genre < 0xff && v1_attachstr(tag, ID3_FRAME_GENRE, 0, genre) == -1) ||
				v1_attachstr(tag, ID3_FRAME_COMMENT, comment, 0) == -1) {
			id3_tag_delete(tag);
			tag = 0;
		}
	}

	return tag;
}

static
struct id3_tag *v2_parse(id3_byte_t const *ptr) {
	struct id3_tag *tag;
	id3_byte_t *mem = 0;

	tag = id3_tag_new();
	if (tag) {
		id3_byte_t const *end;
		id3_length_t size;

		parse_header(&ptr, &tag->version, &tag->flags, &size);

		tag->paddedsize = 10 + size;

		if ((tag->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) &&
				ID3_TAG_VERSION_MAJOR(tag->version) < 4) {
			mem = malloc(size);
			if (mem == 0)
				goto fail;

			memcpy(mem, ptr, size);

			size = id3_util_deunsynchronise(mem, size);
			ptr  = mem;
		}

		end = ptr + size;

		if (tag->flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
			switch (ID3_TAG_VERSION_MAJOR(tag->version)) {
				case 2:
					goto fail;

				case 3: {
					id3_byte_t const *ehptr, *ehend;
					id3_length_t ehsize;

					enum {
						EH_FLAG_CRC = 0x8000  /* CRC data present */
					};

					if (end - ptr < 4)
						goto fail;

					ehsize = id3_parse_uint(&ptr, 4);

					if (ehsize > end - ptr)
						goto fail;

					ehptr = ptr;
					ehend = ptr + ehsize;

					ptr = ehend;

					if (ehend - ehptr >= 6) {
						int ehflags;
						id3_length_t padsize;

						ehflags = id3_parse_uint(&ehptr, 2);
						padsize = id3_parse_uint(&ehptr, 4);

						if (padsize > end - ptr)
							goto fail;

						end -= padsize;

						if (ehflags & EH_FLAG_CRC) {
							unsigned long crc;

							if (ehend - ehptr < 4)
								goto fail;

							crc = id3_parse_uint(&ehptr, 4);

							if (crc != id3_crc_compute(ptr, end - ptr))
								goto fail;

							tag->extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
						}
					}
				}
				break;

				case 4: {
					id3_byte_t const *ehptr, *ehend;
					id3_length_t ehsize;
					unsigned int bytes;

					if (end - ptr < 4)
						goto fail;

					ehptr  = ptr;
					ehsize = id3_parse_syncsafe(&ptr, 4);

					if (ehsize < 6 || ehsize > end - ehptr)
						goto fail;

					ehend = ehptr + ehsize;

					bytes = id3_parse_uint(&ptr, 1);

					if (bytes < 1 || bytes > ehend - ptr)
						goto fail;

					ehptr = ptr + bytes;

					/* verify extended header size */
					{
						id3_byte_t const *flagsptr = ptr, *dataptr = ehptr;
						unsigned int datalen;
						int ehflags;

						while (bytes--) {
							for (ehflags = id3_parse_uint(&flagsptr, 1); ehflags;
									ehflags = (ehflags << 1) & 0xff) {
								if (ehflags & 0x80) {
									if (dataptr == ehend)
										goto fail;
									datalen = id3_parse_uint(&dataptr, 1);
									if (datalen > 0x7f || datalen > ehend - dataptr)
										goto fail;
									dataptr += datalen;
								}
							}
						}
					}

					tag->extendedflags = id3_parse_uint(&ptr, 1);

					ptr = ehend;

					if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE) {
						bytes  = id3_parse_uint(&ehptr, 1);
						ehptr += bytes;
					}

					if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
						unsigned long crc;

						bytes = id3_parse_uint(&ehptr, 1);
						if (bytes < 5)
							goto fail;

						crc = id3_parse_syncsafe(&ehptr, 5);
						ehptr += bytes - 5;

						if (crc != id3_crc_compute(ptr, end - ptr))
							goto fail;
					}

					if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
						bytes = id3_parse_uint(&ehptr, 1);
						if (bytes < 1)
							goto fail;

						tag->restrictions = id3_parse_uint(&ehptr, 1);
						ehptr += bytes - 1;
					}
				}
				break;
			}
		}

		/* frames */

		while (ptr < end) {
			struct id3_frame *frame;

			if (*ptr == 0)
				break;  /* padding */

			frame = id3_frame_parse(&ptr, end - ptr, tag->version);
			if (frame == 0 || id3_tag_attachframe(tag, frame) == -1)
				goto fail;
		}

		if (ID3_TAG_VERSION_MAJOR(tag->version) < 4 &&
				id3_compat_fixup(tag) == -1)
			goto fail;
	}

	if (0) {
fail:
		id3_tag_delete(tag);
		tag = 0;
	}

	if (mem)
		free(mem);

	return tag;
}

/*
 * NAME:	tag->parse()
 * DESCRIPTION:	parse a complete ID3 tag
 */
struct id3_tag *id3_tag_parse(id3_byte_t const *data, id3_length_t length) {
	id3_byte_t const *ptr;
	unsigned int version;
	int flags;
	id3_length_t size;

	assert(data);

	switch (tagtype(data, length)) {
		case TAGTYPE_ID3V1:
			return (length < 128) ? 0 : v1_parse(data);

		case TAGTYPE_ID3V2:
			break;

		case TAGTYPE_ID3V2_FOOTER:
		case TAGTYPE_NONE:
			return 0;
	}

	/* ID3v2.x */

	ptr = data;
	parse_header(&ptr, &version, &flags, &size);

	switch (ID3_TAG_VERSION_MAJOR(version)) {
		case 4:
			if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
				size += 10;
		case 2:
		case 3:
			return (length < 10 + size) ? 0 : v2_parse(data);
	}

	return 0;
}

static
void v1_renderstr(struct id3_tag const *tag, char const *frameid,
				  id3_byte_t **buffer, id3_length_t length)
{
	struct id3_frame *frame;
	id3_ucs4_t const *string;

	frame = id3_tag_findframe(tag, frameid, 0);
	if (frame == 0)
		string = id3_ucs4_empty;
	else {
		if (strcmp(frameid, ID3_FRAME_COMMENT) == 0)
			string = id3_field_getfullstring(&frame->fields[3]);
		else
			string = id3_field_getstrings(&frame->fields[1], 0);
	}

	id3_render_paddedstring(buffer, string, length);
}

/*
 * NAME:	v1->render()
 * DESCRIPTION:	render an ID3v1 (or ID3v1.1) tag
 */
static
id3_length_t v1_render(struct id3_tag const *tag, id3_byte_t *buffer)
{
	id3_byte_t data[128], *ptr;
	struct id3_frame *frame;
	unsigned int i;
	int genre = -1;

	ptr = data;

	id3_render_immediate(&ptr, "TAG", 3);

	v1_renderstr(tag, ID3_FRAME_TITLE,   &ptr, 30);
	v1_renderstr(tag, ID3_FRAME_ARTIST,  &ptr, 30);
	v1_renderstr(tag, ID3_FRAME_ALBUM,   &ptr, 30);
	v1_renderstr(tag, ID3_FRAME_YEAR,    &ptr,  4);
	v1_renderstr(tag, ID3_FRAME_COMMENT, &ptr, 30);

	/* ID3v1.1 track number */

	frame = id3_tag_findframe(tag, ID3_FRAME_TRACK, 0);
	if (frame) {
		unsigned int track;

		track = id3_ucs4_getnumber(id3_field_getstrings(&frame->fields[1], 0));
		if (track > 0 && track <= 0xff) {
			ptr[-2] = 0;
			ptr[-1] = track;
		}
	}

	/* ID3v1 genre number */

	frame = id3_tag_findframe(tag, ID3_FRAME_GENRE, 0);
	if (frame) {
		unsigned int nstrings;

		nstrings = id3_field_getnstrings(&frame->fields[1]);

		for (i = 0; i < nstrings; ++i) {
			genre = id3_genre_number(id3_field_getstrings(&frame->fields[1], i));
			if (genre != -1)
				break;
		}

		if (i == nstrings && nstrings > 0)
			genre = ID3_GENRE_OTHER;
	}

	id3_render_int(&ptr, genre, 1);

	/* make sure the tag is not empty */

	if (genre == -1) {
		for (i = 3; i < 127; ++i) {
			if (data[i] != ' ')
				break;
		}

		if (i == 127)
			return 0;
	}

	if (buffer)
		memcpy(buffer, data, 128);

	return 128;
}

/*
 * NAME:	tag->render()
 * DESCRIPTION:	render a complete ID3 tag
 */
id3_length_t id3_tag_render(struct id3_tag const *tag, id3_byte_t *buffer)
{
	id3_length_t size = 0;
	id3_byte_t **ptr,
			   *header_ptr = 0, *tagsize_ptr = 0, *crc_ptr = 0, *frames_ptr = 0;
	int flags, extendedflags;
	unsigned int i;

	assert(tag);

	if (tag->options & ID3_TAG_OPTION_ID3V1)
		return v1_render(tag, buffer);

	/* a tag must contain at least one (renderable) frame */

	for (i = 0; i < tag->nframes; ++i) {
		if (id3_frame_render(tag->frames[i], 0, 0) > 0)
			break;
	}

	if (i == tag->nframes)
		return 0;

	ptr = buffer ? &buffer : 0;

	/* get flags */

	flags         = tag->flags         & ID3_TAG_FLAG_KNOWNFLAGS;
	extendedflags = tag->extendedflags & ID3_TAG_EXTENDEDFLAG_KNOWNFLAGS;

	extendedflags &= ~ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
	if (tag->options & ID3_TAG_OPTION_CRC)
		extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;

	extendedflags &= ~ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
	if (tag->restrictions)
		extendedflags |= ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;

	flags &= ~ID3_TAG_FLAG_UNSYNCHRONISATION;
	if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION)
		flags |= ID3_TAG_FLAG_UNSYNCHRONISATION;

	flags &= ~ID3_TAG_FLAG_EXTENDEDHEADER;
	if (extendedflags)
		flags |= ID3_TAG_FLAG_EXTENDEDHEADER;

	flags &= ~ID3_TAG_FLAG_FOOTERPRESENT;
	if (tag->options & ID3_TAG_OPTION_APPENDEDTAG)
		flags |= ID3_TAG_FLAG_FOOTERPRESENT;

	/* header */

	if (ptr)
		header_ptr = *ptr;

	size += id3_render_immediate(ptr, "ID3", 3);
	size += id3_render_int(ptr, ID3_TAG_VERSION, 2);
	size += id3_render_int(ptr, flags, 1);

	if (ptr)
		tagsize_ptr = *ptr;

	size += id3_render_syncsafe(ptr, 0, 4);

	/* extended header */

	if (flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
		id3_length_t ehsize = 0;
		id3_byte_t *ehsize_ptr = 0;

		if (ptr)
			ehsize_ptr = *ptr;

		ehsize += id3_render_syncsafe(ptr, 0, 4);
		ehsize += id3_render_int(ptr, 1, 1);
		ehsize += id3_render_int(ptr, extendedflags, 1);

		if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)
			ehsize += id3_render_int(ptr, 0, 1);

		if (extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
			ehsize += id3_render_int(ptr, 5, 1);

			if (ptr)
				crc_ptr = *ptr;

			ehsize += id3_render_syncsafe(ptr, 0, 5);
		}

		if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
			ehsize += id3_render_int(ptr, 1, 1);
			ehsize += id3_render_int(ptr, tag->restrictions, 1);
		}

		if (ehsize_ptr)
			id3_render_syncsafe(&ehsize_ptr, (unsigned long)ehsize, 4);

		size += ehsize;
	}

	/* frames */

	if (ptr)
		frames_ptr = *ptr;

	for (i = 0; i < tag->nframes; ++i)
		size += id3_frame_render(tag->frames[i], ptr, tag->options);

	/* padding */

	if (!(flags & ID3_TAG_FLAG_FOOTERPRESENT) &&
			!(tag->options & ID3_TAG_OPTION_NOPADDING)) {
		if (size < tag->paddedsize)
			size += id3_render_padding(ptr, 0, tag->paddedsize - size);
		else if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION) {
			if (ptr == 0)
				size += 1;
			else {
				if ((*ptr)[-1] == 0xff)
					size += id3_render_padding(ptr, 0, 1);
			}
		}
	}

	/* patch tag size and CRC */

	if (tagsize_ptr)
		id3_render_syncsafe(&tagsize_ptr, (unsigned long)(size - 10), 4);

	if (crc_ptr) {
		id3_render_syncsafe(&crc_ptr, id3_crc_compute(frames_ptr, *ptr - frames_ptr), 5);
	}

	/* footer */

	if (flags & ID3_TAG_FLAG_FOOTERPRESENT) {
		size += id3_render_immediate(ptr, "3DI", 3);
		size += id3_render_binary(ptr, header_ptr + 3, 7);
	}

	return size;
}
